<?php

declare(strict_types=1);

namespace owns\library;

use Cron\CronExpression;
use Exception;
use ReflectionClass;
use owns\facade\OwnsLog;
use ReflectionException;
use think\console\Output;

/**
 * 定时任务
 */
class WorkerCrontab
{
    private static $worker;

    private array $cron;

    private string $warehouse;

    private string $nameSpace;

    private string $cycleFormat;

    public readonly Output $output;

    private function __construct()
    {
        $this->warehouse = app_path() . '/jobs/crontab';
        $this->nameSpace = 'app\\jobs\\crontab\\';
        $this->cycleFormat = 'YmdHi';
        $this->output = new Output();
    }
    public function __clone()
    {
    }
    /**
     * 实例化
     */
    public static function instance(): WorkerCrontab
    {
        if (self::$worker instanceof WorkerCrontab) {
            return self::$worker;
        }
        self::$worker = new self();
        return self::$worker;
    }
    /**
     * 加载任务
     */
    public function loading(): void
    {
        $this->cron = [];
        try {
            foreach (glob($this->warehouse . DIRECTORY_SEPARATOR . "*.php") as $file) {
                // 任务类
                $cronClassName = pathinfo($file, PATHINFO_FILENAME);
                // 创建反射类
                $reflection = new ReflectionClass($this->nameSpace . $cronClassName);
                $reflection->getMethod('toTask');
                $this->cron[$cronClassName] = [
                    'key' => $cronClassName, // 唯一标识符
                    'class' => $reflection->getName(), // 完整class
                    'cron' => $reflection->getStaticPropertyValue('cron'), // 执行周期
                    'name' => $reflection->getStaticPropertyValue('name'), // 任务名称
                    'status' => $reflection->getStaticPropertyValue('status'), // 状态: 1=启用 0=禁用
                    'weigh' => $reflection->getStaticPropertyValue('weigh'), // 排序 desc
                    'run_time' => 0, // 运行时间
                    'run_num' => 0, // 运行次数
                ];
            }
        } catch (ReflectionException $e) {
            $this->output($e->getMessage());
        }
        uasort($this->cron, function ($a, $b) {
            return $a['weigh'] - $b['weigh'];
        });
    }
    /**
     * 执行
     */
    public function run(): void
    {
        $jobsCount = count($this->cron);
        if ($jobsCount == 0) {
            $this->output("无可执行任务");
            return;
        }
        $runtime = time();
        foreach ($this->cron as $key => $job) {
            if ($this->isExecutable($job, $runtime)) {
                $this->execute($key, $job);
            }
        }
    }
    /**
     * 是否可执行
     * @param array $job
     * @param integer $runtime
     */
    private function isExecutable(array $job, int $runtime): bool
    {
        $lastRunTime = date($this->cycleFormat, $job['run_time']);
        $runTime = date($this->cycleFormat, $runtime);
        if ($lastRunTime === $runTime) {
            return false;
        }
        try {
            $cron = CronExpression::factory($job['cron']);
            return $cron->isDue($runTime) ? true : false;
        } catch (Exception) {
            $this->output($job['name'] . ": 执行周期格式错误");
            return false;
        }
    }
    /**
     * 执行指定任务
     * @param string $job
     * @param array $job
     */
    private function execute(string $key, array $job): void
    {
        $className = $job['class'];
        if (!class_exists($className)) {
            $this->output($job['name'] . ": 任务类不存在");
            return;
        }
        try {
            $class = new $className();
            $class->toTask();
            // 更新任务信息
            $this->cron[$key]['run_time'] = time();
            $this->cron[$key]['run_num'] += 1;
            $this->output($job['name'], false);
        } catch (Exception $e) {
            $this->output($job['name'] . ": " . $e->getMessage());
        }
    }
    /**
     * 输出信息
     * @param string $message
     * @param boolean $is_error
     */
    private function output(string $message, $is_error = true): void
    {
        if ($is_error) {
            $tag = '[cron-error] ';
        } else {
            $tag = '[cron-success] ';
        }
        $content = $tag . $message;
        // 系统日志
        OwnsLog::notice($content, 'crontab');
        // 控制台输出
        $this->output->write($content . "\n", false);
    }
}
